/*
 * Parser.
 * Copyright (c) 1998 New Generation Software (NGS) Oy
 *
 * Author: Markku Rossi <mtr@ngs.fi>
 */

/*
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA
 */

/*
 * $Source: /usr/local/cvsroot/ngs/js/jsc/parser.js,v $
 * $Id: parser.js,v 1.26 1998/10/26 15:25:21 mtr Exp $
 */

/*
 * Global functions.
 */

function JSC$parser_reset()
{
	JSC$function = null;
	JSC$global_stmts = null;
	JSC$nested_function_declarations = null;
}


function JSC$parser_parse(stream)
{
	JSC$linenum = 1;
	JSC$filename = stream.name;
	JSC$functions = new Array();
	JSC$global_stmts = new Array();
	JSC$nested_function_declarations = new Array();
	JSC$anonymous_function_count = 0;
	JSC$parser_peek_token_valid = false;
	JSC$num_tokens = 0;
	JSC$num_arguments_identifiers = 0;
	JSC$num_missing_semicolons = 0;

	if (JSC$verbose)
		JSC$message("jsc: parsing");

	while (JSC$parser_peek_token(stream) != JSC$tEOF)
		if (!JSC$parser_parse_source_element(stream))
			JSC$parser_syntax_error();

	if (JSC$verbose) {
		var msg = ("jsc: input stream had " + (JSC$linenum - 1).toString()
				   + " lines, " + JSC$num_tokens.toString() + " tokens");

		if (JSC$num_missing_semicolons > 0)
			msg += (", " + JSC$num_missing_semicolons.toString()
					+ " missing semicolons");

		JSC$message(msg);
	}
}


/*
 * General help functions.
 */

function JSC$parser_syntax_error()
{
	error(JSC$filename + ":" + JSC$linenum.toString() + ": syntax error");
}

/* All warnings are reported through this function. */
function JSC$warning(line)
{
	warning(line);
}

/* All messages are reported throught this function. */
function JSC$message(line)
{
	message(line);
}


function JSC$parser_get_token(stream)
{
	JSC$num_tokens++;

	var token;
	if (JSC$parser_peek_token_valid) {
		JSC$parser_peek_token_valid = false;
		JSC$parser_token_value = JSC$parser_peek_token_value;
		JSC$parser_token_linenum = JSC$parser_peek_token_linenum;
		token = JSC$parser_peek_token_token;
	} else {
		token = JSC$lexer(stream);
		JSC$parser_token_value = JSC$token_value;
		JSC$parser_token_linenum = JSC$token_linenum;
	}

	if (token == JSC$tIDENTIFIER && JSC$parser_token_value == "arguments")
		JSC$num_arguments_identifiers++;

	return token;
}


function JSC$parser_peek_token(stream)
{
	if (JSC$parser_peek_token_valid)
		return JSC$parser_peek_token_token;
	else {
		JSC$parser_peek_token_token = JSC$lexer(stream);
		JSC$parser_peek_token_value = JSC$token_value;
		JSC$parser_peek_token_linenum = JSC$token_linenum;
		JSC$parser_peek_token_valid = true;
		return JSC$parser_peek_token_token;
	}
}


function JSC$parser_get_semicolon_asci(stream)
{
	var token = JSC$parser_peek_token(stream);

	if (token == ';'.charCodeAt(0)) {
		/* Everything ok.  It was there. */
		return JSC$parser_get_token(stream);
	}

	/* No semicolon.  Let's see if we can insert it there. */
	if (token == '}'.charCodeAt(0)
		|| JSC$parser_token_linenum < JSC$parser_peek_token_linenum || token == JSC$tEOF) {
		/* Ok, do the automatic semicolon insertion. */
		if (JSC$warn_missing_semicolon)
			JSC$warning(JSC$filename + ":" + JSC$parser_token_linenum.toString()
						+ ": warning: missing semicolon");
		JSC$num_missing_semicolons++;
		return ';'.charCodeAt(0);
	}

	/* Sorry, no can do. */
	JSC$parser_syntax_error();
}


function JSC$parser_expr_is_left_hand_side(expr)
{
	return (expr.etype == JSC$EXPR_CALL
			|| expr.etype == JSC$EXPR_OBJECT_PROPERTY
			|| expr.etype == JSC$EXPR_OBJECT_ARRAY
			|| expr.etype == JSC$EXPR_NEW
			|| expr.etype == JSC$EXPR_THIS
			|| expr.etype == JSC$EXPR_IDENTIFIER
			|| expr.etype == JSC$EXPR_FLOAT
			|| expr.etype == JSC$EXPR_INTEGER
			|| expr.etype == JSC$EXPR_STRING
			|| expr.etype == JSC$EXPR_REGEXP
			|| expr.etype == JSC$EXPR_ARRAY_INITIALIZER
			|| expr.etype == JSC$EXPR_NULL
			|| expr.etype == JSC$EXPR_TRUE || expr.etype == JSC$EXPR_FALSE);
}


function JSC$parser_parse_source_element(stream)
{
	if (JSC$parser_parse_function_declaration(stream))
		return true;

	var stmt = JSC$parser_parse_stmt(stream);
	if (!stmt)
		return false;

	if (stmt.stype == JSC$STMT_VARIABLE)
		/*
		 * This is a variable declaration at the global level.	These
		 * are actually global variables.
		 */
		stmt.global_level = true;

	JSC$global_stmts.push(stmt);

	return true;
}


function JSC$parser_parse_function_declaration(stream)
{
	var id, args, block;

	if (JSC$parser_peek_token(stream) != JSC$tFUNCTION)
		return false;
	JSC$parser_get_token(stream);

	/* Record how many `arguments' identifiers have been seen so far. */
	var num_arguments_identifiers = JSC$num_arguments_identifiers;

	if (JSC$parser_peek_token(stream) == JSC$tIDENTIFIER) {
		JSC$parser_get_token(stream);
		id = JSC$parser_token_value;
	}

	var ln = JSC$parser_token_linenum;
	var id_given = id;

	if (JSC$nested_function_declarations.length > 0 || id == undefined) {
		/* This is a nested function declaration. */
		id = ".F:" + (JSC$anonymous_function_count++).toString();
	}
	if(id_given == undefined) {
		id_given = id;
	}

	JSC$nested_function_declarations.push(id);

	if (JSC$parser_get_token(stream) != '('.charCodeAt(0))
		JSC$parser_syntax_error();

	/* Formal parameter list opt. */
	args = new Array();
	while (JSC$parser_peek_token(stream) != ')'.charCodeAt(0)) {
		if (JSC$parser_get_token(stream) != JSC$tIDENTIFIER)
			JSC$parser_syntax_error();
		args.push(JSC$parser_token_value);

		var token = JSC$parser_peek_token(stream);
		if (token == ','.charCodeAt(0)) {
			JSC$parser_get_token(stream);
			if (JSC$parser_peek_token(stream) != JSC$tIDENTIFIER)
				JSC$parser_syntax_error();
		} else if (token != ')'.charCodeAt(0))
			JSC$parser_syntax_error();
	}

	if (JSC$parser_get_token(stream) != ')'.charCodeAt(0))
		JSC$parser_syntax_error();

	JSC$parser_peek_token(stream);
	var lbrace_ln = JSC$parser_peek_token_linenum;

	block = JSC$parser_parse_block(stream);
	if (typeof block == "boolean")
		JSC$parser_syntax_error();

	/* Did the function use the `arguments' identifier? */
	var use_arguments = false;
	if (JSC$num_arguments_identifiers > num_arguments_identifiers) {
		use_arguments = true;
		if (JSC$warn_deprecated)
			JSC$warning(JSC$filename + ":" + ln.toString()
						+ ": warning: the `arguments' property of Function "
						+ "instance is deprecated");
	}

	JSC$functions.push(new JSC$function_declaration(ln, lbrace_ln, id,
													id_given, args, block, use_arguments));

	JSC$nested_function_declarations.pop();

	return true;
}


function JSC$parser_parse_block(stream)
{
	var block;

	if (JSC$parser_peek_token(stream) != '{'.charCodeAt(0))
		return false;

	JSC$parser_get_token(stream) != '{'.charCodeAt(0);
	var ln = JSC$parser_peek_token_linenum;

	/* Do we have a statement list? */
	if (JSC$parser_peek_token(stream) != '}'.charCodeAt(0))
		/* Yes we have. */
		block = JSC$parser_parse_stmt_list(stream);
	else
		/* Do we don't */
		block = new Array();

	if (JSC$parser_get_token(stream) != '}'.charCodeAt(0))
		JSC$parser_syntax_error();

	block.linenum = ln;

	return block;
}


function JSC$parser_parse_stmt_list(stream)
{
	var list, done, item;

	list = new Array();
	done = false;

	while (!done) {
		item = JSC$parser_parse_stmt(stream);
		if (typeof item == "boolean") {
			/* Can't parse more statements.  We'r done. */
			done = true;
		} else
			list.push(item);
	}

	return list;
}


function JSC$parser_parse_stmt(stream)
{
	var item, token;

	if (typeof(item = JSC$parser_parse_block(stream)) != "boolean")
		return new JSC$stmt_block(item.linenum, item);
	else if (JSC$parser_parse_function_declaration(stream)) {
		/* XXX The function declaration as statement might be incomplete. */

		if (JSC$nested_function_declarations.length == 0)
			/* Function declaration at top-level statements. */
			return new JSC$stmt_empty(JSC$parser_token_linenum);

		/* Function declaration inside another function. */

		var container_id = JSC$nested_function_declarations.pop();
		JSC$nested_function_declarations.push(container_id);

		var f = JSC$functions[JSC$functions.length - 1];
		var function_id = f.name;
		var given_id = f.name_given;

		return new JSC$stmt_function_declaration(JSC$parser_token_linenum,
												 container_id, function_id, given_id);
	} else if (typeof(item = JSC$parser_parse_variable_stmt(stream)) != "boolean")
		return item;
	else if (typeof(item = JSC$parser_parse_if_stmt(stream)) != "boolean")
		return item;
	else if (typeof(item = JSC$parser_parse_iteration_stmt(stream)) != "boolean")
		return item;
	else if (typeof(item = JSC$parser_parse_expr(stream)) != "boolean") {
		if (item.etype == JSC$EXPR_IDENTIFIER) {
			/* Possible `Labeled Statement'. */
			token = JSC$parser_peek_token(stream);
			if (token == ':'.charCodeAt(0) && item.linenum == JSC$parser_peek_token_linenum) {
				/* Yes it is. */
				JSC$parser_get_token(stream);
				var stmt = JSC$parser_parse_stmt(stream);
				if (!stmt)
					JSC$parser_syntax_error;

				return new JSC$stmt_labeled_stmt(item.linenum, item.value, stmt);
			}
			/* FALLTHROUGH */
		}

		JSC$parser_get_semicolon_asci(stream);
		return new JSC$stmt_expr(item);
	} else {
		token = JSC$parser_peek_token(stream);
		if (token == ';'.charCodeAt(0)) {
			JSC$parser_get_token(stream);
			return new JSC$stmt_empty(JSC$parser_token_linenum);
		} else if (token == JSC$tCONTINUE) {
			JSC$parser_get_token(stream);

			/* Check the possible label. */
			var label = null;
			token = JSC$parser_peek_token(stream);
			if (token == JSC$tIDENTIFIER
				&& JSC$parser_token_linenum == JSC$parser_peek_token_linenum) {
				JSC$parser_get_token(stream);
				label = JSC$parser_token_value;
			}

			item = new JSC$stmt_continue(JSC$parser_token_linenum, label);

			JSC$parser_get_semicolon_asci(stream);

			return item;
		} else if (token == JSC$tBREAK) {
			JSC$parser_get_token(stream);

			/* Check the possible label. */
			var label = null;
			token = JSC$parser_peek_token(stream);
			if (token == JSC$tIDENTIFIER
				&& JSC$parser_token_linenum == JSC$parser_peek_token_linenum) {
				JSC$parser_get_token(stream);
				label = JSC$parser_token_value;
			}

			item = new JSC$stmt_break(JSC$parser_token_linenum, label);

			JSC$parser_get_semicolon_asci(stream);

			return item;
		} else if (token == JSC$tRETURN) {
			JSC$parser_get_token(stream);
			var linenum = JSC$parser_token_linenum;

			if (JSC$parser_peek_token(stream) == ';'.charCodeAt(0)) {
				/* Consume the semicolon. */
				JSC$parser_get_token(stream);
				item = null;
			} else {
				if (JSC$parser_peek_token_linenum > linenum) {
					/*
					 * A line terminator between tRETURN and the next
					 * token that is not a semicolon.  ASCI here.
					 */
					if (JSC$warn_missing_semicolon)
						JSC$warning(JSC$filename + ":" + linenum.toString()
									+ ": warning: missing semicolon");

					JSC$num_missing_semicolons++;
					item = null;
				} else {
					item = JSC$parser_parse_expr(stream);
					if (typeof item == "boolean")
						JSC$parser_syntax_error();

					JSC$parser_get_semicolon_asci(stream);
				}
			}

			return new JSC$stmt_return(linenum, item);
		} else if (token == JSC$tSWITCH) {
			JSC$parser_get_token(stream);
			return JSC$parser_parse_switch(stream);
		} else if (token == JSC$tWITH) {
			JSC$parser_get_token(stream);
			var linenum = JSC$parser_token_linenum;

			if (JSC$parser_get_token(stream) != '('.charCodeAt(0))
				JSC$parser_syntax_error();

			var expr = JSC$parser_parse_expr(stream);
			if (typeof expr == "boolean")
				JSC$parser_syntax_error();

			if (JSC$parser_get_token(stream) != ')'.charCodeAt(0))
				JSC$parser_syntax_error();

			var stmt = JSC$parser_parse_stmt(stream);
			if (typeof stmt == "boolean")
				JSC$parser_syntax_error();

			return new JSC$stmt_with(linenum, expr, stmt);
		} else if (token == JSC$tTRY) {
			JSC$parser_get_token(stream);
			return JSC$parser_parse_try(stream);
		} else if (token == JSC$tTHROW) {
			JSC$parser_get_token(stream);
			var linenum = JSC$parser_token_linenum;

			/*
			 * Get the next token's linenum.  We need it for strict_ecma
			 * warning.
			 */
			JSC$parser_peek_token(stream);
			var peek_linenum = JSC$parser_peek_token_linenum;

			/* The expression to throw. */
			var expr = JSC$parser_parse_expr(stream);
			if (typeof expr == "boolean")
				JSC$parser_syntax_error();

			if (JSC$warn_strict_ecma && peek_linenum > linenum)
				JSC$warning(JSC$filename + ":" + JSC$linenum.toString()
							+ ": warning: ECMAScript don't allow line terminators"
							+ " between `throw' and expression");

			JSC$parser_get_semicolon_asci(stream);

			return new JSC$stmt_throw(linenum, expr);
		} else
			/* Can't parse more.  We'r done. */
			return false;
	}
}


function JSC$parser_parse_switch(stream)
{
	var linenum = JSC$parser_token_linenum;

	if (JSC$parser_get_token(stream) != '('.charCodeAt(0))
		JSC$parser_syntax_error();

	var expr = JSC$parser_parse_expr(stream);
	if (!expr)
		JSC$parser_syntax_error();

	if (JSC$parser_get_token(stream) != ')'.charCodeAt(0))
		JSC$parser_syntax_error();

	if (JSC$parser_get_token(stream) != '{'.charCodeAt(0))
		JSC$parser_syntax_error();

	/* Parse case clauses. */
	var clauses = new Array();
	while (true) {
		var token = JSC$parser_get_token(stream);

		if (token == '}'.charCodeAt(0))
			break;
		else if (token == JSC$tCASE || token == JSC$tDEFAULT) {
			var stmts = new Array();
			stmts.expr = null;

			if (token == JSC$tCASE) {
				stmts.expr = JSC$parser_parse_expr(stream);
				if (!stmts.expr)
					JSC$parser_syntax_error();
			}
			if (JSC$parser_get_token(stream) != ':'.charCodeAt(0))
				JSC$parser_syntax_error();

			stmts.linenum = JSC$parser_token_linenum;

			/* Read the statement list. */
			while (true) {
				token = JSC$parser_peek_token(stream);
				if (token == '}'.charCodeAt(0) || token == JSC$tCASE || token == JSC$tDEFAULT)
					/* Done with this branch. */
					break;

				var stmt = JSC$parser_parse_stmt(stream);
				if (!stmt)
					JSC$parser_syntax_error();

				stmts.push(stmt);
			}

			stmts.last_linenum = JSC$parser_token_linenum;

			/* One clause parsed. */
			clauses.push(stmts);
		} else
			JSC$parser_syntax_error();
	}

	return new JSC$stmt_switch(linenum, JSC$parser_token_linenum, expr, clauses);
}


function JSC$parser_parse_try(stream)
{
	var linenum = JSC$parser_token_linenum;

	var block = JSC$parser_parse_stmt(stream);
	if (!block)
		JSC$parser_syntax_error();

	var try_block_last_linenum = JSC$parser_token_linenum;

	/* Now we must see `catch' or `finally'. */
	var token = JSC$parser_peek_token(stream);
	if (token != JSC$tCATCH && token != JSC$tFINALLY)
		JSC$parser_syntax_error();

	var catch_list = false;
	if (token == JSC$tCATCH) {
		/* Parse catch list. */

		catch_list = new Array();
		catch_list.linenum = JSC$parser_peek_token_linenum;

		while (token == JSC$tCATCH) {
			JSC$parser_get_token(stream);
			var c = new Object();
			c.linenum = JSC$parser_token_linenum;

			if (JSC$parser_get_token(stream) != '('.charCodeAt(0))
				JSC$parser_syntax_error();

			if (JSC$parser_get_token(stream) != JSC$tIDENTIFIER)
				JSC$parser_syntax_error();
			c.id = JSC$parser_token_value;

			c.guard = false;
			if (JSC$parser_peek_token(stream) == JSC$tIF) {
				JSC$parser_get_token(stream);
				c.guard = JSC$parser_parse_expr(stream);
				if (!c.guard)
					JSC$parser_syntax_error();
			}

			if (JSC$parser_get_token(stream) != ')'.charCodeAt(0))
				JSC$parser_syntax_error();

			c.stmt = JSC$parser_parse_stmt(stream);
			if (!c.stmt)
				JSC$parser_syntax_error();

			catch_list.push(c);

			token = JSC$parser_peek_token(stream);
		}

		catch_list.last_linenum = JSC$parser_token_linenum;
	}

	var fin = false;
	if (token == JSC$tFINALLY) {
		/* Parse the finally. */
		JSC$parser_get_token(stream);

		fin = JSC$parser_parse_stmt(stream);
		if (!fin)
			JSC$parser_syntax_error();
	}

	return new JSC$stmt_try(linenum, try_block_last_linenum,
							JSC$parser_token_linenum, block, catch_list, fin);
}


function JSC$parser_parse_variable_stmt(stream)
{
	var list, id, expr, token;

	if (JSC$parser_peek_token(stream) != JSC$tVAR)
		return false;
	JSC$parser_get_token(stream);
	var ln = JSC$parser_token_linenum;

	list = new Array();

	while (true) {
		token = JSC$parser_peek_token(stream);
		if (token == JSC$tIDENTIFIER) {
			JSC$parser_get_token();
			id = JSC$parser_token_value;

			if (JSC$parser_peek_token(stream) == '='.charCodeAt(0)) {
				JSC$parser_get_token(stream);
				expr = JSC$parser_parse_assignment_expr(stream);
				if (typeof expr == "boolean")
					JSC$parser_syntax_error();
			} else
				expr = null;

			list.push(new JSC$var_declaration(id, expr));

			/* Check if we have more input. */
			if (JSC$parser_peek_token(stream) == ','.charCodeAt(0)) {
				/* Yes we have.  */
				JSC$parser_get_token(stream);

				/* The next token must be tIDENTIFIER. */
				if (JSC$parser_peek_token(stream) != JSC$tIDENTIFIER)
					JSC$parser_syntax_error();
			} else {
				/* No, we don't. */
				JSC$parser_get_semicolon_asci(stream);
				break;
			}
		} else {
			/* We'r done. */
			JSC$parser_get_semicolon_asci(stream);
			break;
		}
	}

	/* There must be at least one variable declaration. */
	if (list.length == 0)
		JSC$parser_syntax_error();

	return new JSC$stmt_variable(ln, list);
}


function JSC$parser_parse_if_stmt(stream)
{
	var expr, stmt, stmt2;

	if (JSC$parser_peek_token(stream) != JSC$tIF)
		return false;
	JSC$parser_get_token(stream);
	var ln = JSC$parser_token_linenum;

	if (JSC$parser_get_token(stream) != '('.charCodeAt(0))
		JSC$parser_syntax_error();

	expr = JSC$parser_parse_expr(stream);
	if (typeof expr == "boolean")
		JSC$parser_syntax_error();

	if (JSC$parser_get_token(stream) != ')'.charCodeAt(0))
		JSC$parser_syntax_error();

	stmt = JSC$parser_parse_stmt(stream);
	if (typeof stmt == "boolean")
		JSC$parser_syntax_error();

	if (JSC$parser_peek_token(stream) == JSC$tELSE) {
		JSC$parser_get_token(stream);
		stmt2 = JSC$parser_parse_stmt(stream);
		if (typeof stmt2 == "boolean")
			JSC$parser_syntax_error();
	} else
		stmt2 = null;

	return new JSC$stmt_if(ln, expr, stmt, stmt2);
}


function JSC$parser_parse_iteration_stmt(stream)
{
	var token, expr1, expr2, expr3, stmt;

	token = JSC$parser_peek_token(stream);
	if (token == JSC$tDO) {
		/* do Statement while (Expression); */
		JSC$parser_get_token(stream);
		var ln = JSC$parser_token_linenum;

		stmt = JSC$parser_parse_stmt(stream);
		if (typeof stmt == "boolean")
			JSC$parser_syntax_error();

		if (JSC$parser_get_token(stream) != JSC$tWHILE)
			JSC$parser_syntax_error();

		if (JSC$parser_get_token(stream) != '('.charCodeAt(0))
			JSC$parser_syntax_error();

		expr1 = JSC$parser_parse_expr(stream);
		if (typeof expr1 == "boolean")
			JSC$parser_syntax_error();

		if (JSC$parser_get_token(stream) != ')'.charCodeAt(0))
			JSC$parser_syntax_error();

		JSC$parser_get_semicolon_asci(stream);

		return new JSC$stmt_do_while(ln, expr1, stmt);
	} else if (token == JSC$tWHILE) {
		/* while (Expression) Statement */
		JSC$parser_get_token(stream);
		var ln = JSC$parser_token_linenum;

		if (JSC$parser_get_token(stream) != '('.charCodeAt(0))
			JSC$parser_syntax_error();

		expr1 = JSC$parser_parse_expr(stream);
		if (typeof expr1 == "boolean")
			JSC$parser_syntax_error();

		if (JSC$parser_get_token(stream) != ')'.charCodeAt(0))
			JSC$parser_syntax_error();

		stmt = JSC$parser_parse_stmt(stream);
		if (typeof stmt == "boolean")
			JSC$parser_syntax_error();

		return new JSC$stmt_while(ln, expr1, stmt);
	} else if (token == JSC$tFOR) {
		JSC$parser_get_token(stream);
		var ln = JSC$parser_token_linenum;

		if (JSC$parser_get_token(stream) != '('.charCodeAt(0))
			JSC$parser_syntax_error();

		/* Init */

		var vars = null;

		token = JSC$parser_peek_token(stream);
		if (token == JSC$tVAR) {
			JSC$parser_get_token(stream);
			vars = new Array();

			while (true) {
				/* The identifier. */
				token = JSC$parser_peek_token(stream);
				if (token != JSC$tIDENTIFIER)
					break;

				JSC$parser_get_token(stream);
				var id = JSC$parser_token_value;

				/* Possible initializer. */
				var expr = null;
				if (JSC$parser_peek_token(stream) == '='.charCodeAt(0)) {
					JSC$parser_get_token(stream);
					expr = JSC$parser_parse_assignment_expr(stream);
					if (!expr)
						JSC$parser_syntax_error();
				}

				vars.push(new JSC$var_declaration(id, expr));

				/* Check if we have more input. */
				if (JSC$parser_peek_token(stream) == ','.charCodeAt(0)) {
					/* Yes we have. */
					JSC$parser_get_token(stream);

					/* The next token must be tIDENTIFIER. */
					if (JSC$parser_peek_token(stream) != JSC$tIDENTIFIER)
						JSC$parser_syntax_error();
				} else
					/* No more input. */
					break;
			}

			/* Must have at least one variable declaration. */
			if (vars.length == 0)
				JSC$parser_syntax_error();
		} else if (token != ';'.charCodeAt(0)) {
			expr1 = JSC$parser_parse_expr(stream);
			if (typeof expr1 == "boolean")
				JSC$parser_syntax_error();
		} else
			expr1 = null;

		token = JSC$parser_get_token(stream);
		var for_in = false;

		if (token == ';'.charCodeAt(0)) {
			/* Normal for-statement. */

			/* Check */
			if (JSC$parser_peek_token(stream) != ';'.charCodeAt(0)) {
				expr2 = JSC$parser_parse_expr(stream);
				if (typeof expr2 == "boolean")
					JSC$parser_syntax_error();
			} else
				expr2 = null;

			if (JSC$parser_get_token(stream) != ';'.charCodeAt(0))
				JSC$parser_syntax_error();

			/* Increment */
			if (JSC$parser_peek_token(stream) != ')'.charCodeAt(0)) {
				expr3 = JSC$parser_parse_expr(stream);
				if (typeof expr3 == "boolean")
					JSC$parser_syntax_error();
			} else
				expr3 = null;
		} else if (token == JSC$tIN) {
			/* The `for (VAR in EXPR)'-statement. */

			for_in = true;

			if (expr1) {
				/* The first expression must be an identifier. */
				if (expr1.etype != JSC$EXPR_IDENTIFIER)
					JSC$parser_syntax_error();
			} else {
				/* We must have only one variable declaration. */
				if (vars.length != 1)
					JSC$parser_syntax_error();
			}

			/* The second expressions. */
			expr2 = JSC$parser_parse_expr(stream);
			if (typeof expr2 == "boolean")
				JSC$parser_syntax_error();
		} else
			JSC$parser_syntax_error();

		if (JSC$parser_get_token(stream) != ')'.charCodeAt(0))
			JSC$parser_syntax_error();

		/* Stmt. */
		stmt = JSC$parser_parse_stmt(stream);
		if (typeof stmt == "boolean")
			JSC$parser_syntax_error();

		if (for_in)
			return new JSC$stmt_for_in(ln, vars, expr1, expr2, stmt);

		return new JSC$stmt_for(ln, vars, expr1, expr2, expr3, stmt);
	}
	return false;
}


function JSC$parser_parse_expr(stream)
{
	var expr, expr2;

	if (typeof(expr = JSC$parser_parse_assignment_expr(stream)) == "boolean")
		return false;

	/* Check for the comma expression. */
	while (JSC$parser_peek_token(stream) == ','.charCodeAt(0)) {
		JSC$parser_get_token(stream);
		var ln = JSC$parser_token_linenum;

		if (typeof(expr2 = JSC$parser_parse_assignment_expr(stream)) == "boolean")
			JSC$parser_syntax_error();
		expr = new JSC$expr_comma(ln, expr, expr2);
	}

	return expr;
}


function JSC$parser_parse_assignment_expr(stream)
{
	var expr, expr2, token;

	if (typeof(expr = JSC$parser_parse_conditional_expr(stream)) == "boolean")
		return false;

	if (JSC$parser_expr_is_left_hand_side(expr)) {
		token = JSC$parser_peek_token(stream);
		if (token == '='.charCodeAt(0) || token == JSC$tMULA
			|| token == JSC$tDIVA || token == JSC$tMODA
			|| token == JSC$tADDA || token == JSC$tSUBA
			|| token == JSC$tLSIA || token == JSC$tRSIA
			|| token == JSC$tRRSA || token == JSC$tANDA
			|| token == JSC$tXORA || token == JSC$tORA) {
			JSC$parser_get_token(stream);
			var ln = JSC$parser_token_linenum;

			expr2 = JSC$parser_parse_assignment_expr(stream);
			if (typeof expr2 == "boolean")
				JSC$parser_syntax_error();

			expr = new JSC$expr_assignment(ln, token, expr, expr2);
		}
	}

	if (JSC$optimize_constant_folding && expr.constant_folding)
		return expr.constant_folding();

	return expr;
}


function JSC$parser_parse_conditional_expr(stream)
{
	var expr, expr2, expr3, token;

	if (typeof(expr = JSC$parser_parse_logical_or_expr(stream)) == "boolean")
		return false;

	token = JSC$parser_peek_token(stream);
	if (token == '?'.charCodeAt(0)) {
		JSC$parser_get_token(stream);
		var ln = JSC$parser_token_linenum;

		expr2 = JSC$parser_parse_assignment_expr(stream);
		if (typeof expr2 == "boolean")
			JSC$parser_syntax_error();

		if (JSC$parser_get_token(stream) != ':'.charCodeAt(0))
			JSC$parser_syntax_error();
		expr3 = JSC$parser_parse_assignment_expr(stream);
		if (typeof expr3 == "boolean")
			JSC$parser_syntax_error();

		expr = new JSC$expr_quest_colon(ln, expr, expr2, expr3);
	}

	return expr;
}


function JSC$parser_parse_logical_or_expr(stream)
{
	var expr, expr2;

	if (typeof(expr = JSC$parser_parse_logical_and_expr(stream)) == "boolean")
		return false;

	while (JSC$parser_peek_token(stream) == JSC$tOR) {
		JSC$parser_get_token(stream);
		var ln = JSC$parser_token_linenum;

		expr2 = JSC$parser_parse_logical_and_expr(stream);
		if (typeof expr2 == "boolean")
			JSC$parser_syntax_error();

		expr = new JSC$expr_logical_or(ln, expr, expr2);
	}

	return expr;
}


function JSC$parser_parse_logical_and_expr(stream)
{
	var expr, expr2;

	if (typeof(expr = JSC$parser_parse_bitwise_or_expr(stream)) == "boolean")
		return false;

	while (JSC$parser_peek_token(stream) == JSC$tAND) {
		JSC$parser_get_token(stream);
		var ln = JSC$parser_token_linenum;

		expr2 = JSC$parser_parse_bitwise_or_expr(stream);
		if (typeof expr2 == "boolean")
			JSC$parser_syntax_error();

		expr = new JSC$expr_logical_and(ln, expr, expr2);
	}

	return expr;
}


function JSC$parser_parse_bitwise_or_expr(stream)
{
	var expr, expr2;

	if (typeof(expr = JSC$parser_parse_bitwise_xor_expr(stream)) == "boolean")
		return false;

	while (JSC$parser_peek_token(stream) == '|'.charCodeAt(0)) {
		JSC$parser_get_token(stream);
		var ln = JSC$parser_token_linenum;

		expr2 = JSC$parser_parse_bitwise_xor_expr(stream);
		if (typeof expr2 == "boolean")
			JSC$parser_syntax_error();

		expr = new JSC$expr_bitwise_or(ln, expr, expr2);
	}

	return expr;
}


function JSC$parser_parse_bitwise_xor_expr(stream)
{
	var expr, expr2;

	if (typeof(expr = JSC$parser_parse_bitwise_and_expr(stream)) == "boolean")
		return false;

	while (JSC$parser_peek_token(stream) == '^'.charCodeAt(0)) {
		JSC$parser_get_token(stream);
		var ln = JSC$parser_token_linenum;

		expr2 = JSC$parser_parse_bitwise_and_expr(stream);
		if (typeof expr2 == "boolean")
			JSC$parser_syntax_error();

		expr = new JSC$expr_bitwise_xor(ln, expr, expr2);
	}

	return expr;
}


function JSC$parser_parse_bitwise_and_expr(stream)
{
	var expr, expr2;

	if (typeof(expr = JSC$parser_parse_equality_expr(stream)) == "boolean")
		return false;

	while (JSC$parser_peek_token(stream) == '&'.charCodeAt(0)) {
		JSC$parser_get_token(stream);
		var ln = JSC$parser_token_linenum;

		expr2 = JSC$parser_parse_equality_expr(stream);
		if (typeof expr2 == "boolean")
			JSC$parser_syntax_error();

		expr = new JSC$expr_bitwise_and(ln, expr, expr2);
	}

	return expr;
}


function JSC$parser_parse_equality_expr(stream)
{
	var expr, expr2, token;

	if (typeof(expr = JSC$parser_parse_relational_expr(stream)) == "boolean")
		return false;

	token = JSC$parser_peek_token(stream);
	while (token == JSC$tEQUAL || token == JSC$tNEQUAL
		   || token == JSC$tSEQUAL || token == JSC$tSNEQUAL) {
		JSC$parser_get_token(stream);
		var ln = JSC$parser_token_linenum;

		expr2 = JSC$parser_parse_relational_expr(stream);
		if (typeof expr2 == "boolean")
			JSC$parser_syntax_error();

		expr = new JSC$expr_equality(ln, token, expr, expr2);
		token = JSC$parser_peek_token(stream);
	}

	return expr;
}


function JSC$parser_parse_relational_expr(stream)
{
	var expr, expr2, token;

	if (typeof(expr = JSC$parser_parse_shift_expr(stream)) == "boolean")
		return false;

	token = JSC$parser_peek_token(stream);
	while (token == '<'.charCodeAt(0) || token == '>'.charCodeAt(0) || token == JSC$tLE
		   || token == JSC$tGE) {
		JSC$parser_get_token(stream);
		var ln = JSC$parser_token_linenum;

		expr2 = JSC$parser_parse_shift_expr(stream);
		if (typeof expr2 == "boolean")
			JSC$parser_syntax_error();

		expr = new JSC$expr_relational(ln, token, expr, expr2);
		token = JSC$parser_peek_token(stream);
	}

	return expr;
}


function JSC$parser_parse_shift_expr(stream)
{
	var expr, expr2, token;

	if (typeof(expr = JSC$parser_parse_additive_expr(stream)) == "boolean")
		return false;

	token = JSC$parser_peek_token(stream);
	while (token == JSC$tLSHIFT || token == JSC$tRSHIFT || token == JSC$tRRSHIFT) {
		JSC$parser_get_token(stream);
		var ln = JSC$parser_token_linenum;

		expr2 = JSC$parser_parse_additive_expr(stream);

		if (typeof expr2 == "boolean")
			JSC$parser_syntax_error();

		expr = new JSC$expr_shift(ln, token, expr, expr2);
		token = JSC$parser_peek_token(stream);
	}

	return expr;
}


function JSC$parser_parse_additive_expr(stream)
{
	var expr, expr2, token;

	if (typeof(expr = JSC$parser_parse_multiplicative_expr(stream)) == "boolean")
		return false;

	token = JSC$parser_peek_token(stream);
	while (token == '+'.charCodeAt(0) || token == '-'.charCodeAt(0)) {
		JSC$parser_get_token(stream);
		var ln = JSC$parser_token_linenum;

		expr2 = JSC$parser_parse_multiplicative_expr(stream);
		if (typeof expr2 == "boolean")
			JSC$parser_syntax_error();

		expr = new JSC$expr_additive(ln, token, expr, expr2);
		token = JSC$parser_peek_token(stream);
	}

	return expr;
}


function JSC$parser_parse_multiplicative_expr(stream)
{
	var expr, expr2, token;

	if (typeof(expr = JSC$parser_parse_unary_expr(stream)) == "boolean")
		return false;

	token = JSC$parser_peek_token(stream);
	while (token == '*'.charCodeAt(0) || token == '/'.charCodeAt(0) || token == '%'.charCodeAt(0)) {
		JSC$parser_get_token(stream);
		var ln = JSC$parser_token_linenum;

		expr2 = JSC$parser_parse_unary_expr(stream);
		if (typeof expr2 == "boolean")
			JSC$parser_syntax_error();

		expr = new JSC$expr_multiplicative(ln, token, expr, expr2);
		token = JSC$parser_peek_token(stream);
	}

	return expr;
}


function JSC$parser_parse_unary_expr(stream)
{
	var expr, token;

	token = JSC$parser_peek_token(stream);
	if (token == JSC$tDELETE
		|| token == JSC$tVOID
		|| token == JSC$tTYPEOF
		|| token == JSC$tPLUSPLUS || token == JSC$tMINUSMINUS || token == '+'.charCodeAt(0)
		|| token == '-'.charCodeAt(0)
		|| token == '~'.charCodeAt(0)
		|| token == '!'.charCodeAt(0)) {
		JSC$parser_get_token(stream);
		var ln = JSC$parser_token_linenum;

		expr = JSC$parser_parse_unary_expr(stream);
		if (typeof expr == "boolean")
			JSC$parser_syntax_error();

		return new JSC$expr_unary(ln, token, expr);
	}

	return JSC$parser_parse_postfix_expr(stream);
}


function JSC$parser_parse_postfix_expr(stream)
{
	var expr, token;

	if (typeof(expr = JSC$parser_parse_left_hand_side_expr(stream)) == "boolean")
		return false;

	token = JSC$parser_peek_token(stream);
	if (token == JSC$tPLUSPLUS || token == JSC$tMINUSMINUS) {
		if (JSC$parser_peek_token_linenum > JSC$parser_token_linenum) {
			if (JSC$warn_missing_semicolon)
				JSC$warning(JSC$filename + ":" + JSC$parser_token_linenum.toString()
							+
							": warning: automatic semicolon insertion cuts the expression before ++ or --");
		} else {
			JSC$parser_get_token(stream);
			var ln = JSC$parser_token_linenum;

			return new JSC$expr_postfix(ln, token, expr);
		}
	}

	return expr;
}


function JSC$parser_parse_left_hand_side_expr(stream)
{
	var expr, args, token, expr2;

	if (typeof(expr = JSC$parser_parse_member_expr(stream)) == "boolean")
		return false;

	/* Parse the possible first pair of arguments. */
	if (JSC$parser_peek_token(stream) == '('.charCodeAt(0)) {
		var ln = JSC$parser_peek_token_linenum;

		args = JSC$parser_parse_arguments(stream);
		if (typeof args == "boolean")
			JSC$parser_syntax_error();

		expr = new JSC$expr_call(ln, expr, args);
	} else
		return expr;

	/* Parse to possibly following arguments and selectors. */
	while ((token = JSC$parser_peek_token(stream)) == '('.charCodeAt(0)
		   || token == '['.charCodeAt(0) || token == '.'.charCodeAt(0)) {
		var ln = JSC$parser_peek_token_linenum;

		if (token == '('.charCodeAt(0)) {
			args = JSC$parser_parse_arguments(stream);
			expr = new JSC$expr_call(ln, expr, args);
		} else if (token == '['.charCodeAt(0)) {
			JSC$parser_get_token(stream);

			expr2 = JSC$parser_parse_expr(stream);
			if (typeof expr2 == "boolean")
				JSC$parser_syntax_error();

			if (JSC$parser_get_token(stream) != ']'.charCodeAt(0))
				JSC$parser_syntax_error();

			expr = new JSC$expr_object_array(ln, expr, expr2);
		} else {
			JSC$parser_get_token(stream);
			if (JSC$parser_get_token(stream) != JSC$tIDENTIFIER)
				JSC$parser_syntax_error();

			expr = new JSC$expr_object_property(ln, expr, JSC$parser_token_value);
		}
	}

	return expr;
}


function JSC$parser_parse_member_expr(stream)
{
	var expr, args, token, expr2;

	if (typeof(expr = JSC$parser_parse_primary_expr(stream)) == "boolean") {
		token = JSC$parser_peek_token(stream);

		if (token == JSC$tNEW) {
			JSC$parser_get_token(stream);
			var ln = JSC$parser_token_linenum;

			expr = JSC$parser_parse_member_expr(stream);
			if (typeof expr == "boolean")
				JSC$parser_syntax_error();

			if (JSC$parser_peek_token(stream) == '('.charCodeAt(0)) {
				args = JSC$parser_parse_arguments(stream);
				if (typeof args == "boolean")
					JSC$parser_syntax_error();
			} else
				return new JSC$expr_new(ln, expr, null);

			expr = new JSC$expr_new(ln, expr, args);
		} else
			return false;
	}

	/* Ok, now we have valid starter. */
	token = JSC$parser_peek_token(stream);
	while (token == '['.charCodeAt(0) || token == '.'.charCodeAt(0)) {
		JSC$parser_get_token(stream);
		var ln = JSC$parser_token_linenum;

		if (token == '['.charCodeAt(0)) {
			expr2 = JSC$parser_parse_expr(stream);
			if (typeof expr2 == "boolean")
				JSC$parser_syntax_error();

			if (JSC$parser_get_token(stream) != ']'.charCodeAt(0))
				JSC$parser_syntax_error();

			expr = new JSC$expr_object_array(ln, expr, expr2);
		} else {
			if (JSC$parser_get_token(stream) != JSC$tIDENTIFIER)
				JSC$parser_syntax_error();

			expr = new JSC$expr_object_property(ln, expr, JSC$parser_token_value);
		}

		token = JSC$parser_peek_token(stream);
	}

	return expr;
}


function JSC$parser_parse_primary_expr(stream)
{
	var token, val;

	token = JSC$parser_peek_token(stream);
	var ln = JSC$parser_peek_token_linenum;

	if (JSC$parser_parse_function_declaration(stream)) {
		var f = JSC$functions[JSC$functions.length - 1];
		var function_id = f.name;
		// avoid get_token before return, we return now.
		// see the end of JSC$parser_parse_primary_expr
		return new JSC$expr_identifier(ln, function_id);
	} else

	if (token == JSC$tTHIS)
		val = new JSC$expr_this(ln);
	else if (token == JSC$tIDENTIFIER)
		val = new JSC$expr_identifier(ln, JSC$parser_peek_token_value);
	else if (token == JSC$tFLOAT)
		val = new JSC$expr_float(ln, JSC$parser_peek_token_value);
	else if (token == JSC$tINTEGER)
		val = new JSC$expr_integer(ln, JSC$parser_peek_token_value);
	else if (token == JSC$tSTRING)
		val = new JSC$expr_string(ln, JSC$parser_peek_token_value);
	else if (token == '/'.charCodeAt(0)) {
		/*
		 * Kludge alert!  The regular expression constants (/.../) and
		 * div operands are impossible to distinguish, based only on the
		 * lexical analysis.  Therefore, we need some syntactical
		 * knowledge when the regular expression constants are possible
		 * at all.	This is the place where they can appear.  In all
		 * other places, the character `/' is interpreted as a div
		 * operator.
		 */
		JSC$parser_get_token(stream);

		return new JSC$expr_regexp(ln, JSC$lexer_read_regexp_constant(stream));
	} else if (token == JSC$tNULL)
		val = new JSC$expr_null(ln);
	else if (token == JSC$tTRUE)
		val = new JSC$expr_true(ln);
	else if (token == JSC$tFALSE)
		val = new JSC$expr_false(ln);
	else if (token == '['.charCodeAt(0)) {
		/* Array initializer. */
		/* TODO: SharpVarDefinition_{opt} */

		JSC$parser_get_token(stream);

		var items = new Array();
		var pos = 0;

		while ((token = JSC$parser_peek_token(stream)) != ']'.charCodeAt(0)) {
			if (token == ','.charCodeAt(0)) {
				JSC$parser_get_token(stream);
				items[++pos] = false;
				continue;
			}

			var expr = JSC$parser_parse_assignment_expr(stream);
			if (!expr)
				JSC$parser_syntax_error();

			items[pos] = expr;

			/* Got one expression.	It must be followed by ',' or ']'. */
			token = JSC$parser_peek_token(stream);
			if (token != ','.charCodeAt(0) && token != ']'.charCodeAt(0))
				JSC$parser_syntax_error();
		}

		val = new JSC$expr_array_initializer(ln, items);
	} else if (token == '{'.charCodeAt(0)) {
		/* Object literal. */
		/* TODO: SharpVarDefinition_{opt} */

		JSC$parser_get_token(stream);

		var items = new Array();

		while ((token = JSC$parser_peek_token(stream)) != '}'.charCodeAt(0)) {
			var pair = new Object();

			token = JSC$parser_get_token(stream);

			pair.linenum = JSC$linenum;
			pair.id_type = token;
			pair.id = JSC$parser_token_value;

			if (token != JSC$tIDENTIFIER && token != JSC$tSTRING && token != JSC$tINTEGER)
				JSC$parser_syntax_error();

			if (JSC$parser_get_token(stream) != ':'.charCodeAt(0))
				JSC$parser_syntax_error();

			pair.expr = JSC$parser_parse_assignment_expr(stream);
			if (!pair.expr)
				JSC$parser_syntax_error();

			items.push(pair);

			/*
			 * Got one property, initializer pair.	It must be followed
			 * by ',' or '}'.
			 */
			token = JSC$parser_peek_token(stream);
			if (token == ','.charCodeAt(0)) {
				/* Ok, we have more items. */
				JSC$parser_get_token(stream);

				token = JSC$parser_peek_token(stream);
				if (token != JSC$tIDENTIFIER && token != JSC$tSTRING && token != JSC$tINTEGER)
					JSC$parser_syntax_error();
			} else if (token != '}'.charCodeAt(0) && token)
				JSC$parser_syntax_error();
		}

		val = new JSC$expr_object_initializer(ln, items);
	} else if (token == '('.charCodeAt(0)) {
		JSC$parser_get_token(stream);

		val = JSC$parser_parse_expr(stream);
		if (typeof val == "boolean" || JSC$parser_peek_token(stream) != ')'.charCodeAt(0))
			JSC$parser_syntax_error();
	} else {
		return false;
	}

	JSC$parser_get_token(stream);
	return val;
}


function JSC$parser_parse_arguments(stream)
{
	var args, item;

	if (JSC$parser_peek_token(stream) != '('.charCodeAt(0))
		return false;

	args = new Array();

	JSC$parser_get_token(stream);
	while (JSC$parser_peek_token(stream) != ')'.charCodeAt(0)) {
		item = JSC$parser_parse_assignment_expr(stream);
		if (typeof item == "boolean")
			JSC$parser_syntax_error();
		args.push(item);

		var token = JSC$parser_peek_token(stream);
		if (token == ','.charCodeAt(0))
			JSC$parser_get_token(stream);
		else if (token != ')'.charCodeAt(0))
			JSC$parser_syntax_error();
	}
	JSC$parser_get_token(stream);

	return args;
}


/*
Local variables:
mode: c
End:
*/
